package com.evanflare.core;

import com.evanflare.conf.ConfigKeysConstant;
import com.evanflare.conf.ReadConfig;
import com.evanflare.domain.ReviewDocumentInfo;
import com.evanflare.exception.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * 遍历复习目录中的每一个文档
 * @author Yanion_gwgzh
 */
public class FileFinder {
    public static String reviewDirPath;
    public static String reviewDocumentSuffixList = ReadConfig.getInstance().getConfigValue(ConfigKeysConstant.review_document_suffix_list.name());
    public static String ExcludeDirNameRegex = ReadConfig.getInstance().getConfigValue(ConfigKeysConstant.exclude_directory_regex.name());
    public static final String REVIEW_FLAG = "#review";
    private static final Logger LOGGER = LoggerFactory.getLogger(FileFinder.class);
    static{
        //1 初始化复习目录，需要验证目录存在
        String dirPath = ReadConfig.getInstance().getConfigValue(ConfigKeysConstant.review_documents_directory_path.name());
        File file = new File(dirPath);
        if (file.exists() && file.isDirectory()){
            // 1.1 目录存在
            reviewDirPath = dirPath;
        }else{
            // 1.2 目录不存在
            LOGGER.warn("warning: {} 复习目录不存在。将自动创建新的目录", dirPath);
            // 1.2.1 试图创建对应的dir
            if (!file.mkdirs()) {
                LOGGER.error("fatal: {} 复习目录创建失败，检查目录路径。",dirPath);
                System.exit(-1);
            }
            reviewDirPath = dirPath;
        }
    }
    public List<ReviewDocumentInfo> findAllNeedReviewDocuments(){
        try {
            return findAllNeedReviewDocuments(reviewDirPath);
        }catch (Exception e){
            e.printStackTrace();
            LOGGER.error("fatal: 未知错误。{}", e.toString());
            return null;
        }
    }

    /**
     * 这个方法是一个危险方法，没有对异常情况进行处理,会抛出异常
     * @param reviewDirPath
     * @return 返回复习
     */
    public List<ReviewDocumentInfo> findAllNeedReviewDocuments(String reviewDirPath) throws MyException {
        File dir = new File(reviewDirPath);
        if (!dir.exists() || !dir.isDirectory()) {
            throw new MyException(new FileNotFoundException(reviewDirPath), "路径指向的文件不存在或者不是一个目录。");
        }
        // 1 遍历所有的文件，判断文件是否需要复习
        List<ReviewDocumentInfo> res = new ArrayList<>();
        for (File file : Objects.requireNonNull(dir.listFiles())) {
            if(file.isDirectory()){
                // 1.1 如果是目录，并且没有被排除, 递归执行次函数
                if(!file.getName().matches(ExcludeDirNameRegex)){
                    res.addAll(findAllNeedReviewDocuments(file.getPath()));
                }
            }else{
                // 1.2  如果是文件，并且属于复习文档类型
                String[] split = file.getName().split("[.]");
                if(split == null || split.length < 2 || !reviewDocumentSuffixList.contains(split[1])){
                    continue;
                }else{
                    // 1.2.1 文件属于复习文档，开始分析
                    LOGGER.info("{} 文档属于复习文档，开始分析...", file.getPath());
                    try(FileReader fileReader = new FileReader(file)){
                        BufferedReader bufferedReader = new BufferedReader(fileReader);
                        String firstLine = "";
                        try {
                            // 1.2.1warning  如果是空文件，会报错空指针.
                            firstLine = bufferedReader.readLine().trim();
                        }catch (NullPointerException e){
                            LOGGER.warn("warning: {} 空文件，不视为复习文档。", file.getPath());
                            continue;
                        }
                        /**
                         * 1 在文件首行添加`#review`作为标记符
                         *
                         * 2 在标记符后接空格，以及文档的模式 0表示正在修改模式 1表示稳定文档模式 -1表示复习完毕模式
                         *
                         * 3 在 文档模式之后接空格，以及当前复习进度从0开始递增。
                         */
                        String[] params = firstLine.split(" ");
                        // [1.2.1] 1 检验参数个数
                        if(params.length != 4){
                            LOGGER.warn("warning: {} 复习文档参数个数错误。将重置元信息。", file.getPath());
                            initReviewDocument(file);
                            continue;
                        }
                        // [1.2.1] 2 检测标志
                        if (!REVIEW_FLAG.equals(params[0])) {
                            LOGGER.warn("warning: {} 复习文档没有元信息。将自动设置元信息。", file.getPath());
                            initReviewDocument(file);
                            continue;
                        }
                        // [1.2.1] 3 读取元信息，创建对象
                        int type = Integer.parseInt(params[1]), reviewTimes = Integer.parseInt(params[2]);
                        LocalDateTime lastReviewTimes = LocalDateTime.parse(params[3], DateTimeFormatter.ISO_LOCAL_DATE_TIME);
                        ReviewDocumentInfo reviewDocumentInfo = new ReviewDocumentInfo(
                                file.getName(),
                                file.getPath(),
                                type,
                                reviewTimes,
                                lastReviewTimes
                                );
                        if(reviewDocumentInfo.getType() != -1 && reviewDocumentInfo.getOverReviewHours() > 0) {
                            res.add(reviewDocumentInfo);
                        }
                    } catch (
                            Exception e) {
                        LOGGER.warn("warning: 文件 {} 出现错误。", file.getPath());
                        LOGGER.warn(e.toString());
                        e.printStackTrace();
                    }
                }
            }
        }
        // 2 排序
        Collections.sort(res);
        return res;
    }

    /**
     * 给文件首行添加复习元信息
     * @param file
     */
    private void initReviewDocument(File file){
        StringBuilder builder = new StringBuilder();
        builder.append(REVIEW_FLAG).append(" ").append(1).append(" ").append(0).append(" ");
        String dateTime = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        builder.append(dateTime).append(System.lineSeparator());
        if(file == null || !file.exists() || !file.isFile()){
            LOGGER.error("fatal: {} 文件类型错误，无法添加元信息。", file!=null?file.getParentFile():"null");
            return;
        }
        // 1 创建一个临时文件，将原有内容暂存到临时文件，然后再覆盖原文件内容
        File parentDir = file.getParentFile();
        File tmpFile = new File(parentDir, file.getName() + ".tmp");
        tmpFile.deleteOnExit();
        try(RandomAccessFile raf = new RandomAccessFile(file,"rw");
            FileOutputStream tmpOut = new FileOutputStream(tmpFile);
            FileInputStream tmpIn = new FileInputStream(tmpFile)) {
            raf.seek(0);
            byte[] buf = new byte[64];
            int hasRead = 0;
            while((hasRead = raf.read(buf))>0){
                //把原有内容读入临时文件
                tmpOut.write(buf,0,hasRead);
            }
            raf.seek(0);
            raf.write(builder.toString().getBytes(StandardCharsets.UTF_8));
            //追加临时文件的内容
            while((hasRead = tmpIn.read(buf))>0){
                raf.write(buf,0,hasRead);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
